package fregat.base.net;


import java.io.IOException;
import java.net.ServerSocket;
import java.net.Socket;
import java.net.SocketTimeoutException;
import java.util.Enumeration;
import java.util.Hashtable;
import java.util.ResourceBundle;

import fregat.base.util.Logger;

/**
 * Server thread that allows TCP clients to connect. When a client connects the
 * {@link fregat.base.net.TCPClient} handles sending and receiving of data. The
 * client object has to be created in the implemented method.
 *
 * @author Alexander Mann <br><b><i>ECO_L O G</i> &nbsp Logistiksysteme GmbH</b>
 * @version 1.0
 */
public abstract class TCPServer extends Thread {

  private ServerSocket serverSocket; // socket of server
  private int port; // port of server
  protected Hashtable clients; // table with connected clients
  private boolean shutdown; // shutdown flag
  private Protocol protocol; // used protocol
  private static ResourceBundle resource = ResourceBundle.getBundle("at.ecolog.base.net.resource");
  private int socketTimeout = -1;
  private Logger logger;

  public TCPServer(int port) {
    this(port,Logger.getDefaultCategory());
  }

  /**
   * Initializes the server accept thread.
   *
   * @param port Port, where to wait for clients
   */
  public TCPServer(int port, Logger logger) {
    this.port = port;
    this.logger = logger;
  }

  public void setSocketTimeout(int socketTimeout) {
    this.socketTimeout = socketTimeout;
  }

  protected abstract TCPClient connect(Socket clientSocket);

  /**
   * Waits for connections and starts the client threads.
   */
  public void run() {
    try {
      serverSocket = new ServerSocket(port);
      if(socketTimeout>0) {
        serverSocket.setSoTimeout(socketTimeout);
      }
    } catch (IOException ex) {
      logger.error("TCPServer,run()", ex);
      return;
    }

    logger.info(getName()+", "+resource.getString("TCPServer_run_4")+" "+port);

    while (!shutdown) {
      try {
        Socket clientSocket = serverSocket.accept();

        TCPClient client = connect(clientSocket); // inheriting class creates the client
        String debug;

        if (client != null) {
          debug = resource.getString("TCPServer_run_1");
          client.setProtocol(protocol); // set the protocol used for send and receive
          if (clients == null) {
            clients = new Hashtable();
          }
          clients.put(client.getKey(), client);
          client.start();

        } else {
          debug = resource.getString("TCPServer_run_2");
          clientSocket.close();
        }
        logger.debug(getName() + ", " + debug + " " +
            clientSocket.getInetAddress().toString().substring(1) +
            "@" + clientSocket.getLocalPort());
      } catch (SocketTimeoutException ex) {
        logger.note(getName() +", socket timeout");
      } catch (IOException ex) {
        if (!shutdown) {
          logger.debug("TCPServer,run()", ex);
        }
      }
    }
    try {
      serverSocket.close();
    } catch (Exception ex) {
      logger.error("TCPServer.run",ex);
    }
    logger.debug(getName()+", " + resource.getString("TCPServer_run_3"));
  }

  /**
   * Closes sockets to shutdown server.
   */
  public void shutdown() {
    shutdown = true;
    try {
      if (serverSocket != null) {
        serverSocket.close();
      }
    } catch (IOException ex) {
      logger.error("TCPServer.shutdown",ex);
    }

    shutdownClients();
  }

  /**
   * Closes all clients.
   */
  private void shutdownClients() {
    if(clients != null) {
      Enumeration e = clients.elements();
      while(e.hasMoreElements()) {
        TCPClient client = (TCPClient)e.nextElement();
        client.shutdown();
      }
    }
  }


  /**
   * Resets the server
   */
  public void reset() {
    shutdownClients();
  }

  /**
   * Sets the used send and receive protocol.
   *
   * @param protocol
   */
  public void setProtocol(Protocol protocol) {
    this.protocol = protocol;
  }
}
